#ifndef _FUNDAMENTALTYPES_
#define _FUNDAMENTALTYPES_

#include <string>
#include <vector>
#include <map>
#include <unordered_map>
#include <stdexcept>
#include <cstdint>
#include <cstring>

typedef const char * Bytecode;
typedef std::pair<int, int> MethodKey;

namespace std {
    template<>
    class hash<MethodKey> {
    public:
        inline size_t operator()(const MethodKey& k) const 
        {
            return std::hash<MethodKey::first_type >()(k.first) ^
                  (std::hash<MethodKey::second_type>()(k.second) << 1);
        }
    };
}

typedef std::unordered_map<MethodKey, Bytecode> VTable;

enum ValueType
{
    Undefined,
    Number,
    ObjRef,
    ArrayRef,
    StringRef,
    Object,
    ArrayContents,
    ReturnAddress,
    FramePointer
};

struct ObjectTableElem;
struct StackElem;

typedef StackElem * Array;
typedef std::intptr_t IntType;

struct StackElem
{
    union
    {
        ValueType type;
        char bytes[sizeof(ValueType)];
    };
    union
    {
        IntType value;
        Bytecode ret_addr;
        StackElem * backup_fp;
        ObjectTableElem ** object;
        ObjectTableElem ** array;
        ObjectTableElem ** string;
    };
    StackElem() { }
    StackElem(IntType value)
    : type(Number),
      value(value) { }
    StackElem(ObjectTableElem ** string)
    : type(StringRef),
      string(string) { }
    friend std::ostream& operator<<(std::ostream& os, const StackElem& p);
};

typedef std::unordered_map<int, int> FieldTable;

struct Class
{
    Class() {}

    VTable vtable;
    FieldTable field_offsets;
};

struct ObjectTableElem
{
    ValueType type;
    union
    {
        const VTable * vtable;
        IntType size;
    };
    union
    {
        const FieldTable * fields;
        IntType capacity;
    };
    union
    {
        Array array;
        char * string;
    };

    ObjectTableElem * GCForwardedTo;
    bool GCIsForwarded;

    ObjectTableElem()
    : GCForwardedTo(NULL),
      GCIsForwarded(false)
    { }
};

typedef std::vector<std::string> StringPool;
typedef std::map<Bytecode, std::string> AddrToNameMap;
typedef std::unordered_map<std::string, int> UndefinedFunctions;
typedef std::unordered_map<int, Class> Classes;

class ObjectTable
{
private:
    ObjectTableElem** const fHeapPointers;
    ObjectTableElem** fEndOfTable;
    ObjectTableElem** fCurrentFreeSlot;

    ObjectTableElem** RewindPointerIfNeeded(ObjectTableElem** iPtr)
    {
        if(iPtr == fEndOfTable)
            return fHeapPointers;

        return iPtr;
    }

public:
    ObjectTable(unsigned int iSize)
        : fHeapPointers(new ObjectTableElem*[iSize])
    {
        fEndOfTable = fHeapPointers + iSize;
        fCurrentFreeSlot = fHeapPointers;
        memset(fHeapPointers, 0, sizeof(fHeapPointers[0]) * iSize);
    }

    ~ObjectTable()
    {
        delete [] fHeapPointers;
    }

    // Ulozi pointer na HeapElem do pole a vrati adresu, kde je ten pointer ulozeny.
    // 
    ObjectTableElem** AddReference(ObjectTableElem* iElem)
    {
        ObjectTableElem** lCurrentFreeSlotTmp = fCurrentFreeSlot;

        // Pokusim se najit volne misto, kam pointer ulozim.
        
        bool lFreeSlotFound = false;

        if(*lCurrentFreeSlotTmp == NULL) // OK, je tam volno
        {
            lFreeSlotFound = true;
        }
        else // neni tam volno - jed dal, dokud nenarazis na volne misto nebo se neotocis kolem dokola
        {
            lCurrentFreeSlotTmp++;

            while(lCurrentFreeSlotTmp != fCurrentFreeSlot)
            {
                // dojel jsem na konec - presun se na zacatek
                // poznamka: ano, skutecne se to musi delat na zacatku cyklu - co kdyz fCurrentFreeSlot ukazuje na posledni prvek
                // v poli... hned v prvni iteraci by to cetlo mimo pole
                if(lCurrentFreeSlotTmp == fEndOfTable)
                    lCurrentFreeSlotTmp = fHeapPointers;

                // nasel jsem volne misto, hura
                if(*lCurrentFreeSlotTmp == NULL)
                {
                    lFreeSlotFound = true;
                    break;
                }

                lCurrentFreeSlotTmp++;
            }
        }

        if(!lFreeSlotFound)
            throw std::runtime_error("Prilis mnoho referenci v tabulce referenci.");

        fCurrentFreeSlot = lCurrentFreeSlotTmp;
        *fCurrentFreeSlot = iElem;

        ObjectTableElem** lReturn = fCurrentFreeSlot;

        fCurrentFreeSlot = RewindPointerIfNeeded(fCurrentFreeSlot + 1);
        return lReturn;
    }

    // Vycisti neplatne reference z tabulky.
    // Za neplatne jsou povazovany ty, kde HeapElem::GCIsForwarded == false - tj. to jsou prvky, ktere se pri GC oznacily jako mrtve.
    // Fyzicke vymazani odkazovanych objektu tady delat nebudu, to resi GC, kdyz maze starou haldu.
    // Vycisteni spociva v tom, ze se v tabulce nastavi adresa na HeapElem na 0.
    // Objekty, 
    void GCCleanUp()
    {
        ObjectTableElem** lIterator = fHeapPointers;

        while(lIterator != fEndOfTable)
        {
            if(*lIterator != NULL) // musim taky davat pozor, abych preskocil pripadne volne sloty - tabulka muze byt fragmentovana!
            {
                if(!(*lIterator)->GCIsForwarded) // je to smeti - nastav pointer na 0
                {
                    *lIterator = NULL;
                }
                else // je to prezivsi objekt - zmen ukazatel tak, aby ukazoval do nove haldy
                {
                    *lIterator = (*lIterator)->GCForwardedTo;
                }
            }

            lIterator++;
        }
    }
};

#endif
